package mwt.wow.mpq; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.zip.CRC32; import java.util.zip.Deflater; public class MpqWriter { private MpqArchive archive; private long blockOffset; private long fileSize; private int sectorSize; private int nextSector; private int[] sectorTable; private boolean isSingleUnit; private boolean isCompressed; private boolean isImploded; private CRC32 crc32; private MessageDigest md5; public MpqWriter(MpqArchive archive, long blockOffset, long fileSize) { if (fileSize > 0xffffffffL) { throw new IllegalArgumentException("file too large"); } this.archive = archive; this.blockOffset = blockOffset; this.fileSize = fileSize & 0xffffffffL; isCompressed = true; } public void writeSector(byte[] data) throws IOException { if (nextSector == 0) { prepareWrite(); } if (crc32 != null) { crc32.update(data); } if (md5 != null) { md5.update(data); } int datalen = data.length; byte[] cdata = new byte[datalen]; Deflater deflater = new Deflater(); deflater.setInput(data); deflater.finish(); int cdatalen = deflater.deflate(cdata); if (cdatalen + 1 >= datalen) { sectorTable[nextSector + 1] = sectorTable[nextSector] + datalen; FileWriter writer = new FileWriter(archive.randomAccessFile, archive.archiveOffset + blockOffset + sectorTable[nextSector]); writer.writeBlock(data, 0, datalen); writer.close(); } else { sectorTable[nextSector + 1] = sectorTable[nextSector] + cdatalen + 1; FileWriter writer = new FileWriter(archive.randomAccessFile, archive.archiveOffset + blockOffset + sectorTable[nextSector]); writer.writeInt8(0x02); // deflate writer.writeBlock(cdata, 0, cdatalen); writer.close(); } nextSector++; if (nextSector + 1 == sectorTable.length - 1) { { sectorTable[nextSector + 1] = sectorTable[nextSector]; // TODO: figure out what this if for. last sector is empty on some files, but not always nextSector++; } FileWriter writer = new FileWriter(archive.randomAccessFile, archive.archiveOffset + blockOffset); for (int i = 0; i < sectorTable.length; i++) { writer.writeInt32(sectorTable[i]); } writer.close(); } } private void prepareWrite() { if (isSingleUnit) { sectorSize = (int) fileSize; } else { sectorSize = archive.getSectorSize(); } if ((isCompressed || isImploded) && !isSingleUnit) { sectorTable = new int[(int) ((fileSize + sectorSize - 1) / sectorSize + 1) + 1]; sectorTable[0] = sectorTable.length * 4; } crc32 = new CRC32(); try { md5 = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } } public BlockTableEntry getBlockTableEntry() { if (fileSize == 0 && sectorTable == null) { prepareWrite(); sectorTable[1] = sectorTable[0]; nextSector++; } if (sectorTable == null || nextSector + 1 != sectorTable.length) { throw new IllegalStateException("not written all sectors yet"); } int flags = 0; flags |= 0x80000000; // isFile flags |= 0x04000000; // TODO: what is this for? if (isSingleUnit) { flags |= 0x01000000; } // if (isEncryptionAdjusted) { // flags |= 0x00020000; // } // if (isEncrypted) { // flags |= 0x00010000; // } if (isCompressed) { flags |= 0x00000200; } if (isImploded) { flags |= 0x00000100; } BlockTableEntry blockTableEntry = new BlockTableEntry(blockOffset, sectorTable[nextSector], (int) fileSize, flags); if (crc32 != null) { blockTableEntry.setExtCRC32((int) crc32.getValue()); } if (md5 != null) { blockTableEntry.setExtMD5(md5.digest()); } return blockTableEntry; } }